﻿using STDTO.DataTransferObjects.Interfaces;
using STDTO.DataTransferObjects.Impl;
using STDTO.Interfaces;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Specialized;


namespace STDTO.DataTransferObjects.Base
{
    [Serializable]
    public class SelfTrackingDataTransferObjectCollection<T> : ObservableCollection<T>, ISelfTrackingDataTransferObjectEx<IList<T>>, INotifyPropertyChangedWithHasHandlers
    {
        #region Memebers
        //holds the collection change actions
        private List<CollectionItemAction<T>> m_changeItems = new List<CollectionItemAction<T>>();
        private DataTransferObjectState _state = DataTransferObjectState.Unchanged;
        private const string ON_COLLECTION_CHANGED_PROPERTY = "OnCollectionChanged";
        #endregion

        #region Properties
        public DataTransferObjectState State
        {
            get { return _state; }
            set { _state = value; }
        }
        #endregion

        #region Reconnect Events
        private List<INotifyPropertyChanged> m_childObjects = new List<INotifyPropertyChanged>();
        [OnDeserialized]
        private void ReconnectEvents(StreamingContext context)
        {
            foreach (INotifyPropertyChangedWithHasHandlers item in m_childObjects)
            {
                if (!item.HasHandlers)
                {
                    item.PropertyChanged += new PropertyChangedEventHandler(DataTransferObjectCollection_PropertyChanged);
                }
            }
        }

        public bool HasHandlers
        {
            get
            {
                return PropertyChanged != null;
            }
        }

        protected override event PropertyChangedEventHandler PropertyChanged;

        void DataTransferObjectCollection_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            State = DataTransferObjectState.Modified;
            OnPropertyChanged(e);
        }
        #endregion

        #region Static Methods

        internal static SelfTrackingDataTransferObjectCollection<T> CreateCollectionDto<T>(IList<T> entityCollection, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper, IDtoInstanceCreator dtoInstanceCreator)
        {
            SelfTrackingDataTransferObjectCollection<T> res = dtoInstanceCreator.CreateDtoInstance<IList<T>>(entityCollection) as SelfTrackingDataTransferObjectCollection<T>;
            if (res == null)
            {
                res = new SelfTrackingDataTransferObjectCollection<T>();
            }
            if (entityCollection != null)
            {
                res.Populate(entityCollection, dataTrasferObjectToEntityMapper);
            }
            return res;
        }

        #endregion

        #region Override ObservableCollection<T>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems.Count > 0)
            {

                foreach (T newItem in e.NewItems.Cast<T>())
                {

                    m_changeItems.Add(new CollectionItemAction<T>() { ChangeAction = CollectionChangeAction.Added, Item = newItem, Index = e.NewStartingIndex });
                }

            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {

                foreach (T oldItem in e.OldItems.Cast<T>())
                {
                    m_changeItems.Add(new CollectionItemAction<T>() { ChangeAction = CollectionChangeAction.Removed, Item = oldItem, Index = e.OldStartingIndex });
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Replace)
            {
                //do nothing - allow replace
            }


            //Not supporting for new Reset,move 
            else
            {
                throw new ArgumentException(string.Format("NotifyCollectionChangedAction {0} is not supported by DataTransferObjectCollection", e.Action));
            }
            OnPropertyChanged(new PropertyChangedEventArgs(ON_COLLECTION_CHANGED_PROPERTY));
            base.OnCollectionChanged(e);
        }

        protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            State = DataTransferObjectState.Modified;
            base.OnPropertyChanged(e);
        }

        #endregion

        #region implements IDataTransferObjectEx<List<T>>

        public IList<T> Clone(bool getOriginalValue)
        {
            IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper = GetDataTrasferObjectToEntityMapper();
            return Clone(getOriginalValue, dataTrasferObjectToEntityMapper);
        }

        public IList<T> Clone(bool getOriginalValue, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            IList<T> res = new List<T>();

            //add original items
            foreach (T item in this)
            {
                ISelfTrackingDataTransferObjectEx<T> dtoItem = (ISelfTrackingDataTransferObjectEx<T>)item;
                res.Add(dtoItem.Clone(getOriginalValue, dataTrasferObjectToEntityMapper));
            }
            //if we need to retrieve the original values
            if (getOriginalValue)
            {
                UndoCollectionChanges(getOriginalValue, res, dataTrasferObjectToEntityMapper);
            }
            return res;
        }

        public void ApplyChanges(IList<T> entityList)
        {
            IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper = GetDataTrasferObjectToEntityMapper();
            ApplyChanges(entityList, dataTrasferObjectToEntityMapper);
        }

        public void ApplyChanges(IList<T> entityList, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            //if the state is not nuchange
            if (State != DataTransferObjectState.Unchanged)
            {
                //redo all the colletion operations
                ApplyCollectionChanges(entityList, dataTrasferObjectToEntityMapper);

                //We have the two list synchronized now so we can pass on them
                for (int i = 0; i < this.Count; i++)
                {
                    ISelfTrackingDataTransferObjectEx<T> dtoMatchingItem = (ISelfTrackingDataTransferObjectEx<T>)this[i];
                    //the server side matching entity
                    T matchEnt = entityList[i];

                    dtoMatchingItem.ApplyChanges(matchEnt, dataTrasferObjectToEntityMapper);
                }
            }
        }

        public void Populate(IList<T> entities)
        {
            IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper = GetDataTrasferObjectToEntityMapper();
            Populate(entities, dataTrasferObjectToEntityMapper);

        }

        public void Populate(IList<T> entities, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            foreach (T entity in entities)
            {
                this.Items.Add(CreateNewDtoInner(entity, dataTrasferObjectToEntityMapper));
            }
        }

        #region private methods
        private void UndoCollectionChanges(bool getOriginalValue, IList<T> res, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            for (int i = m_changeItems.Count - 1; i >= 0; i--)
            {
                var collectionItemAction = m_changeItems[i];
                ISelfTrackingDataTransferObjectEx<T> dtoItem = (ISelfTrackingDataTransferObjectEx<T>)collectionItemAction.Item;
                T originalItem = dtoItem.Clone(getOriginalValue, dataTrasferObjectToEntityMapper);
                //the item was added
                if (collectionItemAction.ChangeAction == CollectionChangeAction.Added)
                {
                    res.RemoveAt(collectionItemAction.Index);
                }
                //the item was removed
                else
                {
                    res.Insert(collectionItemAction.Index, originalItem);
                }
            }
        }

        private void ApplyCollectionChanges(IList<T> entityList, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            foreach (var collectionItemAction in m_changeItems)
            {
                ISelfTrackingDataTransferObject<T> dtoItem = (ISelfTrackingDataTransferObject<T>)collectionItemAction.Item;
                
                //the item was added
                if (collectionItemAction.ChangeAction == CollectionChangeAction.Added)
                {
                    T originalItem = dtoItem.Clone(true);
                    entityList.Insert(collectionItemAction.Index, originalItem);
                }
                //the item was removed
                else
                {
                    entityList.RemoveAt(collectionItemAction.Index);
                }

            }
        }

        private T CreateNewDtoInner(T entity, IDataTrasferObjectToEntityMapper dataTrasferObjectToEntityMapper)
        {
            object dtoObject = null;
            if (dataTrasferObjectToEntityMapper.TryGetDataTransferObject(entity, out dtoObject))
            {
                return (T)dtoObject;
            }
            IDtoInstanceCreator dtoInstanceCreator = GetDtoInstanceCreator();
            ISelfTrackingDataTransferObjectEx<T> res = dtoInstanceCreator.CreateDtoInstance<T>(entity) as ISelfTrackingDataTransferObjectEx<T>;
            res.Populate(entity, dataTrasferObjectToEntityMapper);
            ((INotifyPropertyChanged)res).PropertyChanged += new PropertyChangedEventHandler(DataTransferObjectCollection_PropertyChanged);
            m_childObjects.Add((INotifyPropertyChanged)res);
            return (T)res;
        }
        #endregion
      
        #endregion

        #region virtual / abstract method
        protected virtual IDataTrasferObjectToEntityMapper GetDataTrasferObjectToEntityMapper()
        {
            return new DataTrasferObjectToEntityMapper();
        }
        protected virtual IDtoInstanceCreator GetDtoInstanceCreator()
        {
            throw new DtoException("Must override GetDtoInstanceCreator or use UnityDataTransferObjectCollection",null);
        }
        
        #endregion
    }
}
